/*
 * Copyright 2021-2030 itbox.cn All right reserved. This software is the confidential and proprietary information
 * of itbox.cn ("Confidential Information"). You shall not disclose such Confidential Information and shall use it
 * only in accordance with the terms of the license agreement you entered into with itbox.cn.
 */
package cn.itbox.example.test.config;

import cn.itbox.uap.db.util.SpringApplicationContextUtil;
import cn.itbox.uap.util.context.SpringContextUtil;
import com.ulisesbocchio.jasyptspringboot.EncryptablePropertySourceConverter;
import org.jasypt.util.text.AES256TextEncryptor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;

import java.util.Iterator;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @BelongProject uap
 * @BelongPackage cn.itbox.example.test.config
 * @Description TODO
 * @ClassName JasyptPreparedEnvListener
 * @Author xingc
 * @Date 2024/1/21 4:59
 * @Version 1.0.0
 */
@Configuration
public class JasyptPreparedEnvListener implements EnvironmentPostProcessor, Ordered {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        decryptPropertySources(environment);
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }

    public void decryptPropertySources(ConfigurableEnvironment env){
        // 1. get decryptor
        Object decryptor = getDecryptor(env);

        if(decryptor == null){
            return;
        }

        String configPrefix = getConfigPrefix(env);
        String configSuffix = getConfigSuffix(env);
        String[] configAllows = getConfigAllows(env);

        // 2. loop property
        Iterator<PropertySource<?>> iterator = env.getPropertySources().iterator();
        while (iterator.hasNext()) {
            PropertySource<?> source = iterator.next();
            Object o = source.getSource();
            if (o instanceof Map) {
                for (Map.Entry<String, Object> entry : ((Map<String, Object>) o).entrySet()) {
                    String key = entry.getKey();
                    if (shouldDecrypted(configAllows,key)) {
                        String value = env.getProperty(key);
                        if (isEncrypted(value, configPrefix, configSuffix)) {
                            //3.解密
                            String newValue = decrypt(env, decryptor, unwrapEncryptedValue(value, configPrefix, configSuffix));
                            System.setProperty(key, newValue);
                        }
                    }
                }
            }
        }
    }

    private String getSalt(ConfigurableEnvironment env ){
        String salt = env.getProperty("jasypt.encryptor.password");

        if(salt == null || salt.isBlank()) {
            return null;
        }

        return salt;
    }

    private String[] getConfigAllows(ConfigurableEnvironment env ){
        String rst = env.getProperty("jasypt.encryptor.property.config.allows");

        String[] rstArr;

        if(rst == null || rst.isBlank()) {
            rstArr = new String[]{"dubbo."};
            return rstArr;
        }else{
            rstArr = Stream.of(rst.split(",")).map(val->val+".").toArray(String[]::new);
        }

        return rstArr;
    }

    private String getConfigPrefix(ConfigurableEnvironment env ){
        String rst = env.getProperty("jasypt.encryptor.property.config.prefix");

        if(rst == null || rst.isBlank()) {
            rst = env.getProperty("jasypt.encryptor.property.prefix");
        }

        if(rst == null || rst.isBlank()) {
            return "ENC(";
        }

        return rst;
    }

    private String getConfigSuffix(ConfigurableEnvironment env ){
        String rst = env.getProperty("jasypt.encryptor.property.config.suffix");

        if(rst == null || rst.isBlank()) {
            rst = env.getProperty("jasypt.encryptor.property.suffix");
        }

        if(rst == null || rst.isBlank()) {
            return ")";
        }

        return rst;
    }

    private boolean shouldDecrypted(String[] allows,String value){
        if(value == null || value.isBlank()){
            return false;
        }

        String trimValue = value.trim();

        boolean rst = false;

        for(String allow : allows){
            if(trimValue.startsWith(allow)){
                rst = true;
            }
        }

        return rst;
    }

    private boolean isEncrypted(String value,String prefix,String suffix){
        if(value == null || value.isBlank()){
            return false;
        }

        String trimValue = value.trim();
        return trimValue.startsWith(prefix) && trimValue.endsWith(suffix);
    }

    private String unwrapEncryptedValue(String value,String prefix,String suffix){
        return value.substring(prefix.length(),value.length() - suffix.length());
    }

    private String getAlgorithm(ConfigurableEnvironment env){
        String rst = env.getProperty("jasypt.encryptor.algorithm");

        if(rst == null || rst.isBlank()) {
            return "PBEWithHMACSHA512AndAES_256";
        }

        return rst;
    }

    private Object getDecryptor(ConfigurableEnvironment env){

        String algorithm = getAlgorithm(env);

        Object obj = null;

        if(algorithm.equals("PBEWithHMACSHA512AndAES_256")){
            String salt = getSalt(env);
            if(salt != null){
                obj = new AES256TextEncryptor();
                ((AES256TextEncryptor)obj).setPassword(salt);
            }
        }//add other decryptor logic here

        return obj;
    }

    private String decrypt(ConfigurableEnvironment env,Object decryptpr,String value){

        String algorithm = getAlgorithm(env);

        if(algorithm.equals("PBEWithHMACSHA512AndAES_256")){
            AES256TextEncryptor aes256TextEncryptor = (AES256TextEncryptor)decryptpr;
            try{
                return aes256TextEncryptor.decrypt(value);
            }catch (Exception e){
                System.out.println("decrypt failed: " + e);
                return value;
            }
        } //add other decryptor logic here
        return value;
    }
}
